#include <iostream>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <string>
#include <dirent.h>
#include <unistd.h>
#include <vector>
#include <sstream>
#include <fstream>
#include <sys/io.h>
#include <sys/times.h>
#include <iomanip> // setw()
using namespace std;

#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
using namespace cv;

namespace segfg 
{
struct sPara { // parameters for blob analysising
	int minW, maxW, minH, maxH, minArea, maxArea;
	sPara () {
		minW 	= 20;
		maxW 	= 200;
		minH 	= 20;
		maxH 	= 200;
		minArea = 20;
		maxArea = 40000;
	}
};

struct sPosi {
	std::vector<cv::Point> bounder;
	std::vector<cv::Point> poly;
	std::vector<std::vector<cv::Point> > ocBounders;
	std::vector<cv::Mat> ocImgs; // croped and resize images for cnn
	std::vector<int > ocNums; // result of cnn;
};

struct sCandidateFlag {
	int fatherIdx;
	std::vector<cv::Point> poly;
	std::vector<int> HoleIdx;
};

struct DistanceIdx {
	double distance;
	int idx;
};

bool sort_dist(const DistanceIdx v1, const DistanceIdx v2)
{
	return v1.distance > v2.distance; 
}

#define FORE_OTSU 		0
#define FORE_ADAPTIVE 	1
cv::Mat get_global_fore(const cv::Mat &src, int method);

std::vector<sCandidateFlag> blobAnalysis(const Mat &src,
                                         const vector<vector<Point> > &contours,
                                         const vector<Vec4i> &hierarchy);
struct sHole {
	int holeIdx;
	std::vector<cv::Point> holePoly;
	std::vector<int> subObjIdx;
};
bool FindHoles( const Mat &src,
                const vector<vector<Point> > &contours,
                const vector<Vec4i> &hierarchy, 
                std::vector<sHole> &sh);
bool FindSubObj(const Mat &src,
                const vector<vector<Point> > &contours,
                const vector<Vec4i> &hierarchy, 
                std::vector<sHole> &sh);

struct sFlag {
	cv::Mat cropImg;
	cv::Rect oriRect;
	std::vector<cv::Point> oriContour;
	
};
bool getImage(const Mat &src,
              const vector<vector<Point> > &contours,
              const vector<Vec4i> &hierarchy, 
              const std::vector<sHole> &sh,
              std::vector<sFlag> &vFlg);

/*******************************
* segment all the flags from the input image
*/
std::vector<sPosi> segment_flag(const cv::Mat &src) 
{
	// must be a single channel image
	CV_Assert(CV_MAT_TYPE(src.type()) == CV_8UC1);
	sPara para;
	std::vector<sPosi> Positions;
	// get global forground
	Mat Fore = get_global_fore(src, FORE_ADAPTIVE);
	// find contours
	vector<vector<Point> > contours;
	vector<Vec4i> hierarchy;
	findContours(Fore, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE);
	// holes
	std::vector<sHole> sh;
	FindHoles(src, contours, hierarchy, sh);
	// find holes that have 4 sub regions 
	FindSubObj(src, contours, hierarchy, sh);
	// 
	std::vector<sFlag> vFlg;
	getImage(src, contours, hierarchy, sh, vFlg);
	
/*	// select threshold child regions
	std::vector<sCandidateFlag> Candidates = blobAnalysis(src, contours, hierarchy); 
	for (int i = 0; i < (int)Candidates.size(); i++) {
		Rect curRct = boundingRect(contours[Candidates[i].fatherIdx]);
		Mat tmp;
		if (1)
			resize(src(curRct), tmp, Size(), 4.0, 4.0);
		else 
			tmp = src(curRct);
		imshow("win", tmp);
		waitKey(0);
	} */
	
	
	return Positions;
}

/*******************************
*/
bool getImage(const Mat &src,
              const vector<vector<Point> > &contours,
              const vector<Vec4i> &hierarchy, 
              const std::vector<sHole> &sh,
              std::vector<sFlag> &vFlg) {
	vFlg.clear();
	for (int i = 0; i < (int)sh.size(); i++) {
		int c = sh[i].holeIdx;
		float rotAngle;
		Point2f vtx[4];
		RotatedRect box = minAreaRect(contours[c]);
		box.points(vtx);
		if (true) { // get angle
			cv::Mat rot = cv::getRotationMatrix2D(box.center, box.angle, 1.f);
//			rot.at<double>(0, 2) += box.boundingRect().width / 2.f - box.center.x;
//			rot.at<double>(1, 2) += box.boundingRect().height/ 2.f - box.center.y;
			int l = (int)contours[c].size();
			std::vector <Point2f> crpBounder(l);
			for (int j = 0; j < l; j++) {
				crpBounder[j].x = contours[c][j].x * 1.f;
				crpBounder[j].y = contours[c][j].y * 1.f;
			}
			cv::transform(crpBounder, crpBounder, rot);
			if (0) { // debug code
				cv::Mat tmp;
				cv::warpAffine(src, tmp, rot, src.size());
				cvtColor(tmp, tmp, COLOR_GRAY2BGR);
				std::vector<std::vector<cv::Point> > forDraw(1);
				std::vector<cv::Point> abc(l);
				for (int j = 0 ;j < l; j++) {
					abc[j].x = (int)crpBounder[j].x;
					abc[j].y = (int)crpBounder[j].y;
				}
				forDraw[0] = abc;
				drawContours(tmp, forDraw, 0, Scalar(255,0,255), 1, LINE_8);
				imshow("win", tmp);
				waitKey(0);
			}
			cv::Rect rct = boundingRect(crpBounder);
			if (rct.height > rct.width)
				rotAngle = box.angle + 90;
			else
				rotAngle = box.angle;
		}
		cv::Mat rot = cv::getRotationMatrix2D(box.center, rotAngle, 1.f);
		rot.at<double>(0, 2) += box.boundingRect().width / 2.f - box.center.x;
		rot.at<double>(1, 2) += box.boundingRect().height/ 2.f - box.center.y;
		cv::Mat tmp;
		cv::warpAffine(src, tmp, rot, cv::Size(max(box.size.width, box.size.height), min(box.size.width, box.size.height)));
		imshow("win", tmp);
		waitKey(0);
	}
	
	return true;
}

/*******************************
* get global forground
*/
cv::Mat get_global_fore(const cv::Mat &src, int method) {
	cv::Mat ForeReg;
	switch (method) {
	case FORE_OTSU: { // global otsu
		cv::threshold(src, ForeReg, 255, 255, THRESH_BINARY_INV | THRESH_OTSU);
		// erase regions with huge size
		int an = 9;
		cv::Mat element = getStructuringElement(MORPH_RECT, cv::Size(an*2+1, an*2+1), cv::Point(an,an));
		cv::Mat mor;
		cv::morphologyEx(ForeReg, mor, MORPH_OPEN, element);
		cv::subtract(ForeReg, mor, ForeReg);
		mor.release();
		break;
	}
	case FORE_ADAPTIVE: { // Applies an adaptive threshold
		int blockSize = 19;
		double C = 12;
		adaptiveThreshold(src, ForeReg, 255, ADAPTIVE_THRESH_GAUSSIAN_C, THRESH_BINARY_INV, blockSize, C);
		break;
	}
	}
	
	if (0) {
		imshow("win", ForeReg);
		waitKey(0);
	}
	return ForeReg;
}

/********************************
*  blob analysising
*/
std::vector<sCandidateFlag> blobAnalysis(const Mat &src,
                                         const vector<vector<Point> > &contours,
                                         const vector<Vec4i> &hierarchy) 
{
	// select through - whether there are child region inside current region
	std::vector<sCandidateFlag> childRcd;
	childRcd.clear();
	for (int i = 0; i < (int)contours.size(); i++) {
		std::vector<int> HoleIdx;
		int j = -1; 
		int k = -1;
		if (hierarchy[i][2] >= 0 && hierarchy[i][3] < 0) { // have hole
			j = hierarchy[i][2]; 
			if (hierarchy[j][2] >= 0) { // first child
				k = hierarchy[j][2];
				int m = k; // find all children
				while (true) {
					if (contourArea(contours[m]) > 10.f)
						HoleIdx.push_back(m);
					if (hierarchy[m][1] >= 0) 
						m = hierarchy[m][1];
					else break;
				}
				m = k;
				while (true) {
					if (hierarchy[m][0] >= 0) {
						m = hierarchy[m][0];
						if (contourArea(contours[m]) > 10.f)
							HoleIdx.push_back(m);
					} else break;
				}
				if (HoleIdx.size() > 0) {
					sCandidateFlag curRcd;
					curRcd.fatherIdx = i;
					curRcd.HoleIdx = HoleIdx;
					childRcd.push_back(curRcd);
				}
			}
		}
	}
	
	if (0) { // debug code
		Mat tmp(src.rows, src.cols, CV_8UC1, Scalar::all(0));
		for (int i = 0; i < (int)childRcd.size(); i++) {
			drawContours(tmp, contours, childRcd[i].fatherIdx, Scalar(255), -1, LINE_8, hierarchy);
			for (int j = 0; j < (int)childRcd[i].HoleIdx.size(); j++) {
				drawContours(tmp, contours, childRcd[i].HoleIdx[j], Scalar(255), -1, LINE_8, hierarchy);
			}
		} 
		imshow("win", tmp);
		waitKey(0);
	}
	
	// select from the simplicity of geometry
	std::vector<sCandidateFlag> polyRcd;
	for (int i = 0; i < (int)childRcd.size(); i++) {
		vector<vector<Point> > poly(1);
		approxPolyDP(contours[childRcd[i].fatherIdx], poly[0], 3, true);
		int n = (int)poly[0].size();
		if (n == 4 || n == 5) {
			childRcd[i].poly = poly[0];
			polyRcd.push_back(childRcd[i]);
		}
	} 
	
	if (0) { // debug code
		Mat tmp(src.rows, src.cols, CV_8UC1, Scalar::all(0));
		for (int i = 0; i < (int)polyRcd.size(); i++) {
			drawContours(tmp, contours, polyRcd[i].fatherIdx, Scalar(255), -1, LINE_8, hierarchy);
			for (int j = 0; j < (int)polyRcd[i].HoleIdx.size(); j++) {
				drawContours(tmp, contours, polyRcd[i].HoleIdx[j], Scalar(255), -1, LINE_8, hierarchy);
			}
		}
		cvtColor(tmp, tmp, COLOR_GRAY2BGR);
		for (int i = 0; i < (int)polyRcd.size(); i++) {
			for (int j = 0; j < (int)polyRcd[i].poly.size(); j++) {
				circle(tmp, polyRcd[i].poly[j], 2, Scalar(255,0,255), 2);
			}
		} 
		imshow("win", tmp);
		waitKey(0);
	}
	
	return childRcd;
}

/********************************
*/
bool FindHoles( const Mat &src,
                const vector<vector<Point> > &contours,
                const vector<Vec4i> &hierarchy, 
                std::vector<sHole> &sh) {
	sh.clear();
	for (int i = 0; i < (int)contours.size(); i++) {
		if (hierarchy[i][3] < 0) 
			continue; // no parent contour
		vector<vector<Point> > poly(1);
		approxPolyDP(contours[i], poly[0], 3, true);
		if (poly[0].size() == 4) { // must be a rectangle
			sHole cursh;
			cursh.holeIdx = i;
			cursh.holePoly = poly[0];
			sh.push_back(cursh);
		}
	}
	
	if (false) { // debug code
		Mat tmp;
		cvtColor(src, tmp, COLOR_GRAY2BGR);
		for (int i = 0; i < (int)sh.size(); i++) {
			Scalar color11 = Scalar((unsigned)theRNG() & 255, 
			                        (unsigned)theRNG() & 255, 
			                        (unsigned)theRNG() & 255);
			drawContours(tmp, contours, sh[i].holeIdx, color11, -1, LINE_8, hierarchy);
		}
		cout << ">> sh.size() = " << sh.size() << endl;
		imshow("win", tmp);
		waitKey(0);
	}
	
	return true;
}

/********************************
*/
bool FindSubObj(const Mat &src,
                const vector<vector<Point> > &contours,
                const vector<Vec4i> &hierarchy, 
                std::vector<sHole> &sh) {
	std::vector<sHole> shTmp;
	for (int i = 0; i < (int)sh.size(); i++) {
		int k = sh[i].holeIdx;
		if (hierarchy[k][2] < 0) 
			continue; // no child region
		std::vector<int> subRegIdx;
		int n = hierarchy[k][2]; // the first child
		while (true) {
			double area = contourArea(contours[n]);
			if (area > 5.f) 
				subRegIdx.push_back(n);
			if (hierarchy[n][1] >= 0) {
				n = hierarchy[n][1];
			}
			else break;
		}
		n = hierarchy[k][2];
		while (true) {
			if (hierarchy[n][0] >= 0) {
				n = hierarchy[n][0];
				double area = contourArea(contours[n]);
				if (area > 5.f) 
					subRegIdx.push_back(n);
			}
			else break;
		}
		if (subRegIdx.size() >= 4) { // must have 4 child at least
			sHole cursh;
			cursh.holeIdx = k;
			cursh.holePoly = sh[i].holePoly;
			cursh.subObjIdx = subRegIdx;
			shTmp.push_back(cursh);
		}
	}
	
	if (1) { // debug code
		Mat tmp;
		cvtColor(src, tmp, COLOR_GRAY2BGR);
		for (int i = 0; i < (int)shTmp.size(); i++) {
		    int m = shTmp[i].holeIdx;
			drawContours(tmp, contours, m, Scalar(255,0,255), 2, LINE_8);
			cout << "++++ " << i << endl;
			for (int j = 0; j < (int)shTmp[i].subObjIdx.size(); j++) {
				Scalar color11 = Scalar((unsigned)theRNG() & 255, 
		                            	(unsigned)theRNG() & 255, 
		                            	(unsigned)theRNG() & 255);
				int n = shTmp[i].subObjIdx[j];
				drawContours(tmp, contours, n, color11, -1, LINE_8, hierarchy);
				cout << "-------- " << j << endl;
			}
		}
		imshow("win", tmp);
		waitKey(0);
	}
	
	sh = shTmp;
	return true;
}

/********************************
*/
bool SelectSubObj(const Mat &src,
                  const vector<vector<Point> > &contours,
                  const vector<Vec4i> &hierarchy, 
                  std::vector<sHole> &sh) {
	std::vector<sHole> shTmp;
	for (int i = 0; i < (int)sh.size(); i++) {
		cv:Point2f vtx[4];
		RotatedRect box = minAreaRect(contours[sh[i].holeIdx]);
		box.points(vtx);
		for (int j = 0; j < (int)sh[i].subObjIdx.size(); j++) {
			
		}
	}

	sh = shTmp;
	return true;
}

} // namespace segfg

